// TypeScript 是面向对象的 JavaScript。
// 类描述了所创建的对象共同的属性和方法。
// TypeScript 支持面向对象的所有特性，比如 类、接口等。
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  eat(): void {
    console.log("吃东西");
  }
}
// 创建实例对象
var coder = new Person("张三");
// 访问属性或者方法
coder.eat();

// 类的继承
// TypeScript 支持继承类，即我们可以在创建类的时候继承一个已存在的类，这个已存在的类称为父类，继承它的类称为子类。
// 类继承使用关键字 extends，子类除了不能继承父类的私有成员(方法和属性)和构造函数，其他的都可以继承。
// TypeScript 一次只能继承一个类，不支持继承多个类，但 TypeScript 支持多重继承（A 继承 B，B 继承 C）。
class Root {
  str: string;
}
class Child extends Root {}
class Leaf extends Child {} // 多重继承，继承了 Child 和 Root 类

var obj = new Leaf();
obj.str = "hello";
console.log(obj.str);
// 继承类的方法重写
// 类继承后，子类可以对父类的方法重新定义，这个过程称之为方法的重写。
// 其中 super 关键字是对父类的直接引用，该关键字可以引用父类的属性和方法。
class Person1 {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHello(): void {
    console.log("person1 hello");
  }
}
class Person2 extends Person1 {
  sayHello(): void {
    super.sayHello();
    console.log("Person2 say hello");
  }
}
var p2 = new Person2("p2");
p2.sayHello();

// static
// static 关键字用于定义类的数据成员（属性和方法）为静态的，静态成员可以直接通过类名调用。
class staticCalss {
  static staticName: string = "staticName";
  static syaStaticName = (): void => {
    console.log("sya" + this.staticName);
  };
}
staticCalss.syaStaticName();

// instanceof 使用和js相同 判断对象是否属于某个指定类型
class TestIntanceOf {}
var testIntanceOf = new TestIntanceOf();
console.log(testIntanceOf instanceof TestIntanceOf);

// 访问控制修饰符，由于属性能被任意修改，因此需要限制
// TypeScript 中，可以使用访问控制符来保护对类、变量、方法和构造方法的访问。TypeScript 支持 3 种不同的访问权限。
// public（默认） : 公有，可以在任何地方被访问。
// protected : 受保护，可以被其自身以及其子类访问。
// private : 私有，只能被其定义所在的类访问。
// 可以通过方法将私有方法暴露在实力中方法
class TestP {
  public name: string;
  protected age: number;
  private sex: string;
  constructor(name: string, age: number, sex: string) {
    this.name = name;
    this.age = age;
    this.sex = sex;
  }
  getSex(): string {
    return this.sex;
  }
  getAge(): number {
    return this.age;
  }
  setAge(age: number): void {
    // 判断年龄是否合法
    if (age > 0) {
      this.age = age;
    }
  }
}
class ChildTest extends TestP {
  getParentSex(): number {
    return this.age;
  }
}
var testp = new TestP("李四", 19, "男");
console.log(testp.name);
// console.log(testp.age);
// console.log(testp.sex); Property 'sex' is private and only accessible within class 'TestP'.
console.log(testp.getAge());
console.log(testp.getSex());
testp.setAge(20);
console.log(testp.getAge());
testp.setAge(-20);
console.log(testp.getAge());
const chil = new ChildTest("张三", 19, "女");
console.log(chil.getParentSex());

// 类和接口：类可以实现接口,接口主要实现对类的限制
// 接口在定义类的时候可以去限制类的结构
// 接口中所有属性都不能有实际的值，接口只定义对象的结构，而不考虑实际值
// 接口中所有的方法都是抽象方法
interface TestInterface {
  str: string;
  sayStr(): void;
}
class TestStr implements TestInterface {
  str: string;
  constructor(str: string) {
    this.str = str;
  }
  sayStr(): void {
    console.log(this.str);
  }
}
var testStr = new TestStr("hrllo str");
testStr.sayStr();

// 抽象类 专门用来被继承的类，不能实例化
abstract class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  // 抽象方法，希望具体实现由子类来实现,没有方法体，子类必须实现该抽象方法
  abstract sayHello(): void;
}
class Cat extends Animal {
  sayHello() {
    console.log("喵喵喵~~");
  }
}
// 接口和抽象类的区别
// 1、关键字不同
// 2、抽象类中可以有抽象方法，可以有普通方法，抽象方法必须继承实现，但是接口中都是抽象方法，必须实现
// 3、接口主要定义一种标准，限制类来实现接口

// 泛型---在定义类和函数时候，如果遇到类型不明确就可以使用
//  普通方法
function ft(a: number): number {
  return 1;
}
function fn<T>(a: T) {
  return a;
}
// 可以直接调用具有泛型的函数
var a = fn(10); // 不指定泛型，js可以对类型自动进行推断
console.log("a", a);
console.log(fn<String>("hello"));
// 泛型可以同时指定多个
function fn2<T, K>(a: T, b: K): T {
  console.log(b);
  return a;
}
console.log(fn2<number, string>(123, "helloworld"));

// T entends Inter 表示泛型T 必须是 Inter 实现类或者子类
interface Inter {
  length: number;
}
function fn3<T extends Inter>(a: T): number {
  return a.length;
}
console.log(fn3("hello"));
class MyClass<T> {
  constructor(public name: string) {
    this.name = name;
  }
}
const mc = new MyClass<string>("hello Myclass");
